// LruHashtable - a Hashtable that expires least-recently-used objects
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/

//
// moved to the net.matuschek.util package by Daniel Matuschek
//

package net.matuschek.util;

import java.util.Enumeration;
import java.util.Hashtable;

/** 
 * A Hashtable that expires least-recently-used objects.
 * 
 * <p>Use just like java.util.Hashtable, except that the initial-capacity
 * parameter is required.  Instead of growing bigger than that size,
 * it will throw out objects that haven't been looked at in a while.
 * </p>
 *
 * @author Jef Poskanzer
 * @author Daniel Matuschek
 * @version $Id: LruHashtable.java,v 1.3 2002/05/31 14:45:56 matuschd Exp $
 *
 * @see java.util.Hashtable
 */

public class LruHashtable extends Hashtable
{
  
  // Number of buckets.
  private static final int nBuckets = 2;
  
  // Load factor.
  private float loadFactor;
  
  // When count exceeds this threshold, expires the old table.
  private int threshold;
  
  // Capacity of each bucket.
  private int eachCapacity;
  
  // The tables.
  private Hashtable oldTable;
  private Hashtable newTable;
  
  /// Constructs a new, empty hashtable with the specified initial 
  // capacity and the specified load factor.
  // Unlike a plain Hashtable, an LruHashtable will never grow or
  // shrink from this initial capacity.
  // @param initialCapacity the initial number of buckets
  // @param loadFactor a number between 0.0 and 1.0, it defines
  //		the threshold for expiring old entries
  // @exception IllegalArgumentException If the initial capacity
  // is less than or equal to zero.
  // @exception IllegalArgumentException If the load factor is
  // less than or equal to zero.
  public LruHashtable( int initialCapacity, float loadFactor )
  {
    // We have to call a superclass constructor, but we're not actually
    // going to use it at all.  The only reason we want to extend Hashtable
    // is for type conformance.  So, make a parent hash table of minimum
    // size and then ignore it.
    super( 1 );
    
    if ( initialCapacity <= 0 || loadFactor <= 0.0 )
      throw new IllegalArgumentException();
    this.loadFactor = loadFactor;
    threshold = (int) ( initialCapacity * loadFactor ) - 1;
    eachCapacity = initialCapacity / nBuckets + 1;
    oldTable = new Hashtable( eachCapacity, loadFactor );
    newTable = new Hashtable( eachCapacity, loadFactor );
  }
  
  /// Constructs a new, empty hashtable with the specified initial 
  // capacity.
  // Unlike a plain Hashtable, an LruHashtable will never grow or
  // shrink from this initial capacity.
  // @param initialCapacity the initial number of buckets
  public LruHashtable( int initialCapacity )
  {
    this( initialCapacity, 0.75F );
  }
  
  /// Returns the number of elements contained in the hashtable. 
  public int size()
  {
    return newTable.size() + oldTable.size();
  }
  
  /// Returns true if the hashtable contains no elements.
  public boolean isEmpty()
  {
    return size() == 0;
  }
  
  /// Returns an enumeration of the hashtable's keys.
  // @see LruHashtable#elements
  // @see Enumeration
  public synchronized Enumeration keys()
  {
    return new LruHashtableEnumerator( oldTable, newTable, true );
  }
  
  /// Returns an enumeration of the elements. Use the Enumeration methods 
  // on the returned object to fetch the elements sequentially.
  // @see LruHashtable#keys
  // @see Enumeration
  public synchronized Enumeration elements()
  {
    return new LruHashtableEnumerator( oldTable, newTable, false );
  }
  
  /// Returns true if the specified object is an element of the hashtable.
  // This operation is more expensive than the containsKey() method.
  // @param value the value that we are looking for
  // @exception NullPointerException If the value being searched 
  // for is equal to null.
  // @see LruHashtable#containsKey
  public synchronized boolean contains( Object value )
  {
    if ( newTable.contains( value ) )
      return true;
    if ( oldTable.contains( value ) )
      {
	// We would like to move the object from the old table to the
	// new table.  However, we need keys to re-add the objects, and
	// there's no good way to find all the keys for the given object.
	// We'd have to enumerate through all the keys and check each
	// one.  Yuck.  For now we just punt.  Anyway, contains() is
	// probably not a commonly-used operation.
	return true;
      }
    return false;
  }
  
  /// Returns true if the collection contains an element for the key.
  // @param key the key that we are looking for
  // @see LruHashtable#contains
  public synchronized boolean containsKey( Object key )
  {
    if ( newTable.containsKey( key ) )
      return true;
    if ( oldTable.containsKey( key ) )
      {
	// Move object from old table to new table.
	Object value = oldTable.get( key );
	newTable.put( key, value );
	oldTable.remove( key );
	return true;
      }
    return false;
  }
  
  /// Gets the object associated with the specified key in the 
  // hashtable.
  // @param key the specified key
  // @returns the element for the key or null if the key
  // 		is not defined in the hash table.
  // @see LruHashtable#put
  public synchronized Object get( Object key )
  {
    Object value;
    value = newTable.get( key );
    if ( value != null )
      return value;
    value = oldTable.get( key );
    if ( value != null )
      {
	// Move object from old table to new table.
	newTable.put( key, value );
	oldTable.remove( key );
	return value;
      }
    return null;
  }
  
  /// Puts the specified element into the hashtable, using the specified
  // key.  The element may be retrieved by doing a get() with the same key.
  // The key and the element cannot be null. 
  // @param key the specified key in the hashtable
  // @param value the specified element
  // @exception NullPointerException If the value of the element 
  // is equal to null.
  // @see LruHashtable#get
  // @return the old value of the key, or null if it did not have one.
  public synchronized Object put( Object key, Object value )
  {
    Object oldValue = newTable.put( key, value );
    if ( oldValue != null )
      return oldValue;
    oldValue = oldTable.get( key );
    if ( oldValue != null )
      oldTable.remove( key );
    else
      {
	if ( size() >= threshold )
	  {
	    // Rotate the tables.
	    oldTable = newTable;
	    newTable = new Hashtable( eachCapacity, loadFactor );
	  } 
      }
    return oldValue;
  }
  
  /// Removes the element corresponding to the key. Does nothing if the
  // key is not present.
  // @param key the key that needs to be removed
  // @return the value of key, or null if the key was not found.
  public synchronized Object remove( Object key )
  {
    Object oldValue = newTable.remove( key );
    if ( oldValue == null )
      oldValue = oldTable.remove( key );
    return oldValue;
  }
  
  /// Clears the hash table so that it has no more elements in it.
  public synchronized void clear()
  {
    newTable.clear();
    oldTable.clear();
  }
  
  /// Creates a clone of the hashtable. A shallow copy is made,
  // the keys and elements themselves are NOT cloned. This is a
  // relatively expensive operation.
  public synchronized Object clone()
  {
    LruHashtable n = (LruHashtable) super.clone();
    n.newTable = (Hashtable) n.newTable.clone();
    n.oldTable = (Hashtable) n.oldTable.clone();
    return n;
  }
  
  // toString() can be inherited.
  
}


class LruHashtableEnumerator implements Enumeration
{
  Enumeration oldEnum;
  Enumeration newEnum;
  boolean old;
  
  LruHashtableEnumerator( Hashtable oldTable, Hashtable newTable, boolean keys )
  {
    if ( keys )
      {
	oldEnum = oldTable.keys();
	newEnum = newTable.keys();
      }
    else
      {
	oldEnum = oldTable.elements();
	newEnum = newTable.elements();
      }
    old = true;
  }
  
  public boolean hasMoreElements()
  {
    boolean r;
    if ( old )
      {
	r = oldEnum.hasMoreElements();
	if ( ! r )
	  {
	    old = false;
	    r = newEnum.hasMoreElements();
	  }
      }
    else
      r = newEnum.hasMoreElements();
    return r;
  }
  
  public Object nextElement()
  {
    if ( old )
      return oldEnum.nextElement();
    return newEnum.nextElement();
  }
  
}

